import datetime
import hashlib
import random
import time
import requests
import xmltodict
from libs.utils.common import Struct
from libs.utils import db
class WxPayConfig(object):
    def __init__(self, body="****", price=0.01, res_ip="", notify_url="", order_no="", attach="", code="",config_id=0):
        pay_config=db.default.pay_config_wechat.get(id=config_id)
        self.app_id = pay_config.app_id if pay_config else ""  # 微信开放平台审核通过的应用APP_ID
        self.app_key = pay_config.app_key if pay_config else "" # API密钥
        self.mch_id = pay_config.mch_id if pay_config else "" # 微信支付分配的商户号
        self.app_secret = pay_config.app_secret if pay_config else "" # APPID对应的接口密码
        self.redirect_uri=pay_config.redirect_uri if pay_config else ""
        self.gateway = "https://api.mch.weixin.qq.com/pay/"  # 微信接口网关
        self.scope = "snapi_base"  # jsapi支付获取code需要参数
        self.get_code_url = "https://open.weixin.qq.com/connect/oauth2/authorize"  # jsapi获取code的请求地址，会自动回调到设定的地址
        self.get_open_id_url = "https://api.weixin.qq.com/sns/oauth2/access_token"  # 通过code获取openid的地址
        self.body = body  # 商品的简单描述
        self.total_fee = int(price * 100)  # 订单总金额单位为分
        self.spbill_create_ip = res_ip  # 用户客户端ip
        self.notify_url = notify_url+pay_config.pay_notify if pay_config else "" # 支付结果异步通知地址，不能带参数
        self.attach = attach  # 附加属性
        self.out_trade_no = order_no  # 商户的订单号
        self.code = code

    @staticmethod
    def create_nonce_str():
        """生成随机字符串"""
        return str(random.randint(0, 10000))

    @staticmethod
    def create_trade_no():
        """创建用户订单号"""
        no = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
        no += "%03d" % random.randint(0, 999)
        return no

    def create_sign(self, data):
        """生成参数签名"""
        keys = [k for k in data.keys() if k != 'sign']
        keys.sort()
        sign = "&".join("%s=%s" % (k, data[k]) for k in keys if data[k])
        sign += "&key=%s" % self.app_key
        m = hashlib.md5()
        sign = sign.encode(encoding='utf-8')
        m.update(sign)
        sign = m.hexdigest().upper()
        return sign

    def check_sign(self, d):
        """校验签名是否正确"""
        sign1 = d.get('sign')
        if not sign1:
            return False
        sign2 = self.create_sign(d)
        return sign1 == sign2

    @staticmethod
    def smart_str(s):
        if isinstance(s, str):
            return s.encode('utf-8')
        return s

    @staticmethod
    def trans_dict_to_xml(data):
        """将 dict对象转换成微信支付交互所需的XML格式数据"""
        xml = []
        for k in sorted(data.keys()):
            v = data.get(k)
            if k == 'detail' and not v.startswith('<![CDATA['):
                v = '<![CDATA[{}]]>'.format(v)
            xml.append('<{key}>{value}</{key}>'.format(key=k, value=v))
        return '<xml>{}</xml>'.format(''.join(xml))

    def create_url_for_code(self,vue_url):
        """
        创建能接收到code的url，
        redirect_uri: 是接收code的地址，一般是当前地址, 该域名需要微信鉴权.
        城后成功访问后会在url后得到code
        """
        url = f"{self.get_code_url}?appid={self.app_id}&redirect_uri={vue_url}{self.redirect_uri}" \
              f"&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect"
        return url

    def create_url_for_open_id(self, code):
        """
        code: jsapi支付获取open_id需要的code
        创建能接收到access_token, openid的url，
        成功请求后，会得到json数据，内部含有access_token和openid等
        """
        url = f"{self.get_open_id_url}?appid={self.app_id}&secret={self.app_secret}" \
              f"&code={code}&grant_type=authorization_code"
        return url

    def get_open_id(self, code):
        """
        得到openid: sapi需要的，open_id每个用户一个，需要与微信交互生成
        """
        url = self.create_url_for_open_id(code)
        response = requests.get(url)
        oauth_info = response.json() if response else None
        if oauth_info:
            return oauth_info.get("openid", None)
        return None

    @staticmethod
    def result(resp=None, message=""):
        """结果处理
        1先判断请求微信接口返回状态是否正常
        2判断接口返回return_code 是否为SUCCESS
        3判断业务结果 result_code 是否为 SUCCESS
        """
        out = Struct()
        out.data = {}
        out.status = False
        out.message = message
        if not resp:
            return out
        if resp.status_code != 200:
            # 微信接口状态值异常
            out.message = '微信下单接口状态码异常: %s' % resp.status_code
            return out
        parse = xmltodict.parse(resp.content, encoding='utf-8').get("xml")
        if not parse:
            out.message = '微信下单接口未正常解析到数据 content={}'.format(parse)
            return out
        parse = Struct(parse)
        if parse.return_code != 'SUCCESS':
            # 当前支付微信未返回成功状态
            out.message = parse.return_msg
            return out
        result_code = parse.result_code
        err_code_des = parse.err_code_des
        if result_code != 'SUCCESS':
            # 业务结果微信未返回成功状态
            out.message = err_code_des
            return out
        out.data = parse
        out.status = True
        return out

    def payExecute(self, trade_type="NATIVE", code=""):
        """
        进行预支付请求
        trade_type支付类型分为4种：
            JSAPI： 必须传递code，获得openid后。 请求该接口从犯你会的数据中获取prepay_id和nonce_str。 前端进行调用微信的js(WeixinJSBridge)处理。
            MWEB:  H5支付，请求成功后返回mweb_url， 通过mweb_url调起微信支付中间页，进行支付，
                    支付结束返回商户页面，商户页面需要主动发起支付结果的请求。适用于H5页面
            NATIVE: 二维码支付，返回二维码地址， 需要将返回的url转换成二维码让用户扫码支付---适用于微信公众号和pc
        """
        openid = ""
        if self.code:
            openid = self.get_open_id(self.code)
        args = dict(
            appid=self.app_id,
            mch_id=self.mch_id,
            nonce_str=self.create_nonce_str(),
            body=self.body,
            openid=openid,
            total_fee=self.total_fee,
            spbill_create_ip=self.spbill_create_ip,
            notify_url=self.notify_url,
            trade_type=trade_type,
            attach=self.attach,
            out_trade_no=self.out_trade_no
        )
        if trade_type == "JSAPI":
            args["sign_type"] = "MD5"
        args["sign"] = self.create_sign(args)
        r = requests.post(self.gateway + 'unifiedorder', data=self.trans_dict_to_xml(args).encode())
        r_json = self.result(r)
        if not r_json.status:
            return None, r_json.message
        if trade_type == "JSAPI":
            res_data = r_json.data
            data = dict(
                appId=res_data["appid"],
                timeStamp=int(time.time()),
                nonceStr=res_data["nonce_str"],
                package="prepay_id={prepay_id}".format(prepay_id=res_data['prepay_id']),
                signType="MD5",
            )
            data["paySign"] = self.create_sign(data)
            return data, None
        return r_json.data, None
